from pynput import keyboard
import queue
import time
import threading
import asyncio, asyncvnc
from enum import Enum

'''
202412-29 14:59 当前这条技术路线行不通，看来得键盘监听线程实时维护按键set
ver0.3开始

代码参考链接：
    Python 并发编程（四）：详解 Python 线程消息通信机制 https://zhuanlan.zhihu.com/p/267740197
    Python: 使用 asyncio 实现异步线程间的通信https://blog.csdn.net/Liu_Jilong/article/details/139316874
    python官方《Coroutines and Tasks》https://docs.python.org/3/library/asyncio-task.html#asyncio.gather
    pynput官方文档：https://pynput.readthedocs.io/en/latest/index.html
    《AsyncVNC: Asynchronous VNC for Python》：https://asyncvnc.readthedocs.io/en/latest/index.html#asyncvnc.Keyboard.hold

虚拟机环境搭建配置参考链接：
    VMware下载（要下载的是  VMware Workstation Pro）：https://blog.csdn.net/qq_39192827/article/details/85872025
    搭建VNC环境：
        虚拟机使用的VNC工具：TightVNC
        VMware开启VNC：https://blog.csdn.net/qq_29752857/article/details/135517329
        连接VNC的地址：127.0.0.1
    解决directX报错：
        foxhole无法启动。因为“direct 11 feature level 10 未开启”
        这个错误实际上是因为虚拟机无法调用主机显卡。所以通过安装vmware tools可以解决
        下载vmware tools，下载地址：https://packages.vmware.com/tools/releases/latest/windows/，并按照这个教程，将vmware tools的iso文件挂载到虚拟机里面，随后完成安装 https://docs.vmware.com/cn/VMware-Workstation-Pro/17.6/com.vmware.ws.using.doc/GUID-391BE4BF-89A9-4DC3-85E7-3D45F5124BC7.html

曾经走歪了的技术路线：
VNCdotool，这个工具不够强大：https://vncdotool.readthedocs.io/en/latest/intro.html#quick-start
配置虚拟机网络模式（实际上不需要配置）：https://blog.csdn.net/qq_39192827/article/details/85872025

'''
async def VNCconsumer():
    async with asyncvnc.connect('localhost') as client:
        while True:
            keyArr = holdingKeySet2.list()
            print('当前按键按下情况:')
            print(keyArr)
            # 按下按键
            with client.keyboard.hold(*keyArr):
                # 等待keyboard线程发送更新按键按下情况的消息给我
                vncHoldCallEvent.wait()
            # 离开with代码块，此时所有按键被VNC释放，然后重新从循环头部进入时，按键会被client.keyboard.hold方法再次按下
            # 重置event，使得event.wait()起到阻塞作用
            vncHoldCallEvent.clear()

# keyboard的消费者线程
class ConsumerThread(threading.Thread):
    def __init__(self, name):
        super().__init__()
        self.name = name

    def run(self):
        print('Thread: {} start at {}'.format(self.name, time.ctime(time.time())))

        # 启动VNC协程（这个协程内部会阻塞线程。这个写法很屎，但我不熟悉协程，python多线程，keyboard库，asyncVNC这些代码，所以暂时想不出更好的）
        asyncio.run(VNCconsumer())

# 定义按键映射关系。只有定义为此字典的key的按键才会被监听，并映射为另一个按键发送给虚拟机
keyMappingDict = {'i': 'w', 'j': 'a', 'k': 's', 'l': 'd', ';': ',', '\'': '.'}

def on_press(key):
    try:
        # 只监听ASWD这4个按钮
        if(key.char is not None and key.char in keyMappingDict):
            if(not holdingKeySet2.__contains__(keyMappingDict[key.char])):
                # 如果 holdingKeySet 中不存在这个按键，则加入set中，并发送信号让VNC线程更新按钮按下情况
                holdingKeySet2.add(keyMappingDict[key.char])
                vncHoldCallEvent.set() # 发送信号让VNC线程更新按钮按下情况
                print('按下 {0} 映射为 {1}'.format(key.char, keyMappingDict[key.char]))
    except AttributeError:
        print('special key {0} pressed'.format(key))

def on_release(key):
    try:
        # 只监听ASWD这4个按钮
        if(key.char is not None and key.char in keyMappingDict):
            # 将释放的按键塞进队列中
            if(holdingKeySet2.__contains__(keyMappingDict[key.char])):
                # 如果 holdingKeySet 中存在这个按键，则从set中移除此按钮，并发送信号让VNC线程更新按钮按下情况
                holdingKeySet2.remove(keyMappingDict[key.char])
                vncHoldCallEvent.set() # 发送信号让VNC线程更新按钮按下情况
                print('释放 {0} 映射为 {1}'.format(key.char, keyMappingDict[key.char]))
    except AttributeError:
        print('special key {0} pressed'.format(key))


class ThreadSafeSet:
    def __init__(self):
        self._set = set()
        self._lock = threading.Lock()

    def add(self, item):
        with self._lock:
            self._set.add(item)

    def remove(self, item):
        with self._lock:
            self._set.remove(item)

    def discard(self, item):
        with self._lock:
            self._set.discard(item)
    
    def list(self):
        with self._lock:
            return list(self._set)

    def __contains__(self, item):
        with self._lock:
            return item in self._set

    def __len__(self):
        with self._lock:
            return len(self._set)

holdingKeySet2 = ThreadSafeSet()

vncHoldCallEvent = threading.Event()
vncHoldCallEvent.clear()

threads = []
[threads.append(ConsumerThread(str(i))) for i in range(1,2)]

# 启动所有线程
[t.start() for t in threads]


# 在主线程启动按键监听 Collect events until released
with keyboard.Listener(
        on_press=on_press,
        on_release=on_release) as listener:
    listener.join()

